package com.region.http.client.core;

import com.region.common.adapter.logging.LoggerAdapter;
import com.region.common.adapter.logging.LoggerAdapterFactory;
import com.region.common.utils.ClassUtils;
import com.region.core.annotation.Region;
import com.region.core.env.listener.PropertyRefreshListener;
import com.region.core.scanner.ClassScanner;
import com.region.core.scanner.DefaultClassScanner;
import com.region.http.client.annotation.Priority;
import com.region.http.client.config.ClientConfig;
import com.region.http.client.config.CustomClientConfigurer;
import com.region.http.client.exception.NotFoundInterfaceException;
import com.region.http.client.protector.RequestProtector;
import com.region.http.client.request.HttpExecutor;
import com.region.http.client.resolver.parameter.MultipartParameterResolver;
import com.region.http.client.resolver.parameter.ParameterResolver;
import com.region.http.client.resolver.result.*;
import com.region.plugin.Interceptor;

import java.lang.reflect.Proxy;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;

/**
 * The Region http client factory
 *
 * @author liujieyu
 * @date 2023/6/10 17:19
 * @desciption
 */
public final class RegionClientFactory implements AutoCloseable {

    private LoggerAdapter loggerAdapter;

    private Map<Class, Object> proxyInstance = new ConcurrentHashMap<>(8);

    private ClientProxyHandler proxyHandler = new ClientProxyHandler();

    private ClientConfig config;

    public RegionClientFactory(ClientConfig config, List<Interceptor> executorInterceptor, List<ResultResolver> resultResolvers,
                               List<ParameterResolver> parameterResolvers, List<PropertyRefreshListener> listeners, ExecutorService executorService,
                               List<HttpExecutor> executors, List<RequestProtector> protectors, List<CustomClientConfigurer> configurers) {
        this.config = config;
        proxyHandler.setConfig(config);
        proxyHandler.setExecutorInterceptor(executorInterceptor);
        proxyHandler.setResultResolvers(resultResolvers);
        proxyHandler.setParameterResolvers(parameterResolvers);
        proxyHandler.setListeners(listeners);
        proxyHandler.setThreadPoolExecutor(executorService);
        proxyHandler.setExecutors(executors);
        proxyHandler.setProtectors(protectors);
        proxyHandler.setConfigurerRegisters(configurers);
        initialize();
    }

    /**
     * Initial chemical plant
     */
    protected void initialize() {
        try {
            proxyHandler.initialize();
            loggerAdapter = LoggerAdapterFactory.getLogger(getClass());
            ClassScanner scanner = new DefaultClassScanner();
            String[] basePackages = config.getBasePackages();
            if (basePackages != null) {
                for (String basePackage : basePackages) {
                    Set<Class> classes = scanner.scanningClasses(basePackage);
                    for (Class clazz : classes) {
                        loadClazz(clazz);
                    }
                }
            } else {
                loggerAdapter.warn("The client config base packages is null.");
            }
        } catch (Throwable e) {
            throw e;
        } finally {
            // complete loading
            proxyHandler.complete();
        }
    }

    /**
     * parse class object
     * @param clazz
     */
    private void loadClazz(Class<?> clazz) {
        if (ClassUtils.getInterfaceClass(clazz, Region.class) != null) {
            if (proxyInstance.containsKey(clazz)) {
                loggerAdapter.warn("Interface class duplication, the class is [" + clazz + "]");
            } else {
                // parse class object
                proxyHandler.loadClazz(clazz);
                proxyInstance.put(clazz, newInstance(clazz));
            }
        } else {
            loggerAdapter.warn("The class [" + clazz + "] no has Region annotation.");
        }
    }

    /**
     * Get the client proxy object based on the class object
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> T client(Class<T> clazz) {
        Object object = proxyInstance.get(clazz);
        if (object == null) {
            loggerAdapter.info("The attempt reload class object.");
            loadClazz(clazz);
            object = proxyInstance.get(clazz);
            if (object == null) {
                loggerAdapter.error("The proxy object of interface not found in instances collection");
                throw new NotFoundInterfaceException();
            }
        }
        return (T) object;
    }

    /**
     * The refresh environment info
     *
     * @param key
     * @param value
     */
    public void refreshEnv(String key, String value) {
        proxyHandler.refresh(key, value);
    }
    /**
     * <b> create Object of interface by Dynamic Proxy </b>
     *
     * @param clazz
     * @param <T>
     * @return
     */
    private <T> T newInstance(Class<T> clazz) {
        return (T) Proxy.newProxyInstance(ClassUtils.getClassLoader(), new Class[]{clazz}, new RegionClientProxy(proxyHandler));
    }

    public static RegionClientFactoryBuilder builder() {
        return new RegionClientFactoryBuilder();
    }

    /**
     * support try with resources
     * @throws Exception
     */
    @Override
    public void close() throws Exception {
        proxyHandler.close();
    }

    public static final class RegionClientFactoryBuilder {

        private ClientConfig config;

        /**
         * The interceptor of http executor
         */
        private List<Interceptor> executorInterceptor = new LinkedList<>();

        /**
         * The collection of result resolver
         */
        private List<ResultResolver> resultResolvers = new LinkedList<>();

        /**
         * The collection of parameter resolver
         */
        private List<ParameterResolver> parameterResolvers = new LinkedList<>();

        /**
         * The property source listener
         */
        private List<PropertyRefreshListener> listeners = new LinkedList<>();

        private List<HttpExecutor> executors = new LinkedList<>();

        private List<RequestProtector> protectors = new LinkedList<>();
        /**
         * The asynchronous execute pool executor
         */
        private ExecutorService executorService;

        /**
         * The custom configurer
         */
        private List<CustomClientConfigurer> customClientConfigurers;

        private RegionClientFactoryBuilder() {}

        public RegionClientFactoryBuilder config(ClientConfig config) {
            this.config = config;
            return this;
        }

        public RegionClientFactoryBuilder registerExecutor(HttpExecutor executor) {
            executors.add(executor);
            return this;
        }

        public RegionClientFactoryBuilder registerProtector(RequestProtector protector) {
            protectors.add(protector);
            return this;
        }

        public RegionClientFactoryBuilder interceptor(Interceptor interceptor) {
            executorInterceptor.add(interceptor);
            executorInterceptor.sort(Comparator.comparing(Interceptor::order));
            return this;
        }

        public RegionClientFactoryBuilder resultResolver(ResultResolver resolver) {
            this.resultResolvers.add(resolver);
            this.resultResolvers.sort(Comparator.comparingInt(Priority::getPriority));
            return this;
        }

        public RegionClientFactoryBuilder parameterResolver(ParameterResolver resolver) {
            this.parameterResolvers.add(resolver);
            this.parameterResolvers.sort(Comparator.comparingInt(Priority::getPriority));
            return this;
        }

        public RegionClientFactoryBuilder listener(PropertyRefreshListener listener) {
            listeners.add(listener);
            return this;
        }

        public RegionClientFactoryBuilder asynchronousThreadPoolExecutor(ExecutorService executorService) {
            if (executorService != null) {
                this.executorService = executorService;
            }
            return this;
        }

        public RegionClientFactoryBuilder customConfigurer(CustomClientConfigurer configurer) {
            if (configurer != null) {
                if (customClientConfigurers == null) {
                    customClientConfigurers = new LinkedList<>();
                }
                customClientConfigurers.add(configurer);
            }
            return this;
        }

        public RegionClientFactory build() {
            // The default result resolver
            this.resultResolver(new VoidResultResolver());
            this.resultResolver(new ObjectResultResolver());
            this.resultResolver(new BinaryResultResolver(config));
            this.resultResolver(new BasicResultResolver());
            this.resultResolver(new StringResultResolver());
            // The default parameter resolver
            this.parameterResolver(new MultipartParameterResolver());
            return new RegionClientFactory(config, executorInterceptor, resultResolvers, parameterResolvers, listeners,
                    executorService, executors, protectors, customClientConfigurers);
        }
    }
}
